﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using TorchView.GameContent.Data;

namespace TorchView.GameContent.Skills
{
  public sealed class SkillEvent : IDataSerializable
  {
    public const string BLOCK_NAME = "EVENT";
    public static readonly string[] DECENDANT_TYPES = new string[] { "AFFIXES", "AFFIXESREMOVE", "EFFECTS", "EFFECTSREMOVE", "EXECUTE_SKILL" };

    #region Defaults
    const string File_Default = "";
    const bool Attaches_Default = false;
    const float WeaponDmgPct_Default = 0f;
    const float SoakScalePct_Default = 100f;
    const string DamageType_Default = "";
    const bool UseDPS_Default = false;
    const bool NoStealEffects_Default = true;
    const int DurationOverrideMS_Default = 0;
    const float TargetAhead_Default = 0f;
    const bool AttachLineEmitter_Default = false;
    const bool AttachLineEmitterToMaxDistance_Default = false;
    const bool LineEmitterCollides_Default = false;
    const bool DmgRequiresLOS_Default = false;
    const bool AttachOwner_Default = false;
    const bool AttachToTarget_Default = false;
    const bool AttachIgnoresUnit_Default = false;
    const bool AttachIgnoresObstruction_Default = false;
    const bool ScaleAttachDistance_Default = false;
    const bool PlaceOnTarget_Default = false;
    const bool ApplyEffects_Default = false;
    const bool AlwaysApplyEffects_Default = false;
    const bool MakePet_Default = false;
    const bool MatchesPetDuration_Default = false;
    const bool CastsFromTarget_Default = false;
    const bool StatsHidden_Default = false;
    const string FireFromBone_Default = "";
    const string FireAtBone_Default = "";
    const bool CanClone_Default = false;
    const bool TargetCorpses_Default = false;
    const int CloneCount_Default = 0;
    const float MinDelay_Default = 0f;
    const float MaxDelay_Default = 0f;
    const int MaxUnitsHit_Default = 0;
    const int ExecuteSkillCount_Default = 0;
  #endregion

    public SkillEvent()
    {
      Decendants = new List<IDataSerializable>(4);
      TargetSpecificUnits = new List<string>(4);
    }

    public void LoadFromDataBlock(DataBlock block)
    {
      if (block == null)
        throw new ArgumentNullException("block");
      if (!block.Name.StartsWith(BLOCK_NAME))
        throw new ArgumentException("Incorrect block type, only " + BLOCK_NAME + " is supported.");

      Data = block;
      EventType = block.Name;
      File = block.GetPropertyValue("FILE", File_Default);
      Attaches = block.GetPropertyValue("ATTACHES", Attaches_Default);
      WeaponDmgPct = block.GetPropertyValue("WEAPONDAMAGEPCT", WeaponDmgPct_Default);
      SoakScalePct = block.GetPropertyValue("SOAKSCALEPCT", SoakScalePct_Default);
      DamageType = block.GetPropertyValue("DAMAGE_TYPE", DamageType_Default);
      UseDPS = block.GetPropertyValue("USEDPS", UseDPS_Default);
      NoStealEffects = block.GetPropertyValue("NOSTEALEFFECTS", NoStealEffects_Default);
      DurationOverrideMS = block.GetPropertyValue("DURATIONOVERRIDEMS", DurationOverrideMS_Default);
      TargetAhead = block.GetPropertyValue("TARGET_AHEAD_OF_UNIT", TargetAhead_Default);
      AttachLineEmitter = block.GetPropertyValue("ATTACH_LINE_EMITTER", AttachLineEmitter_Default);
      AttachLineEmitterToMaxDistance = block.GetPropertyValue("ATTACH_LINE_EMITTER_TO_MAX_DISTANCE", AttachLineEmitterToMaxDistance_Default);
      LineEmitterCollides = block.GetPropertyValue("LINE_EMITTER_COLLIDES", LineEmitterCollides_Default);
      DmgRequiresLOS = block.GetPropertyValue("DAMAGE_REQUIRES_LOS", DmgRequiresLOS_Default);
      AttachOwner = block.GetPropertyValue("ATTACHOWNER", AttachOwner_Default);
      AttachToTarget = block.GetPropertyValue("ATTACHTOTARGET", AttachToTarget_Default);
      AttachIgnoresUnit = block.GetPropertyValue("ATTACHIGNORESUNITS", AttachIgnoresUnit_Default);
      AttachIgnoresObstructions = block.GetPropertyValue("ATTACHIGNORESOBSTRUCTIONS", AttachIgnoresObstruction_Default);
      ScaleAttachDistance = block.GetPropertyValue("SCALEATTACHDISTANCE", ScaleAttachDistance_Default);
      PlaceOnTarget = block.GetPropertyValue("PLACEONTARGET", PlaceOnTarget_Default);
      ApplyEffects = block.GetPropertyValue("APPLYEFFECTS", ApplyEffects_Default);
      AlwaysApplyEffects = block.GetPropertyValue("APPLYEFFECTSALWAYS", AlwaysApplyEffects_Default);
      MakePet = block.GetPropertyValue("MAKEPET", MakePet_Default);
      MatchesPetDuration = block.GetPropertyValue("MATCH_PET_DURATION", MatchesPetDuration_Default);
      CastsFromTarget = block.GetPropertyValue("CASTFROMTARGET", CastsFromTarget_Default);
      StatsHidden = block.GetPropertyValue("STATSHIDDEN", StatsHidden_Default);
      FireFromBone = block.GetPropertyValue("FIREFROMBONE", FireFromBone_Default);
      FireAtBone = block.GetPropertyValue("FIREATBONE", FireAtBone_Default);
      CanClone = block.GetPropertyValue("CAN_CLONE", CanClone_Default);
      TargetCorpses = block.GetPropertyValue("TARGETCORPSES", TargetCorpses_Default);
      CloneCount = block.GetPropertyValue("CLONECOUNT", CloneCount_Default);
      MinDelay = block.GetPropertyValue("MINDELAY", MinDelay_Default);
      MaxDelay = block.GetPropertyValue("MAXDELAY", MaxDelay_Default);
      MaxUnitsHit = block.GetPropertyValue("MAX_UNITS_HIT", MaxUnitsHit_Default);
      ExecuteSkillCount = block.GetPropertyValue("EXECUTE_SKILL_COUNT", ExecuteSkillCount_Default);

      TargetSpecificUnits.Clear();
      var targetUnitsBlock = block.GetSubBlock("TARGET_SPECIFIC_UNITS");
      if (targetUnitsBlock != null)
        TargetSpecificUnits.AddRange(targetUnitsBlock.GetProperties("UNIT").Select(p => p.Value as string).Where(s => s != null));

      Decendants.Clear();
      Decendants.AddRange(block.SubBlocks.Select(GetDecendant).Where(d => d != null));
    }

    private static IDataSerializable GetDecendant(DataBlock block)
    {
      switch (block.Name)
      {
        case SkillAffixes.BLOCK_NAME:
          SkillAffixes affixes = new SkillAffixes();
          affixes.LoadFromDataBlock(block);
          return affixes;
        case SkillAffixesRemove.BLOCK_NAME:
          SkillAffixesRemove affixesRemove = new SkillAffixesRemove();
          affixesRemove.LoadFromDataBlock(block);
          return affixesRemove;
        case SkillEffects.BLOCK_NAME:
          SkillEffects effects = new SkillEffects();
          effects.LoadFromDataBlock(block);
          return effects;
        case SkillEffectsRemove.BLOCK_NAME:
          SkillEffectsRemove effectsRemove = new SkillEffectsRemove();
          effectsRemove.LoadFromDataBlock(block);
          return effectsRemove;
        case ExecuteSkill.BLOCK_NAME:
          ExecuteSkill executeSkill = new ExecuteSkill();
          executeSkill.LoadFromDataBlock(block);
          return executeSkill;
        default:
          break;
      }
      return null;
    }

    public DataBlock MakeDataBlock()
    {
      var block = Data == null ? new DataBlock(EventType) : Data;
      block.SetPropertyValue("FILE", File);
      block.SetOrClearPropertyValue(Attaches != Attaches_Default, "ATTACHES", Attaches);
      block.SetOrClearPropertyValue(WeaponDmgPct != WeaponDmgPct_Default, "WEAPONDAMAGEPCT", WeaponDmgPct);
      block.SetOrClearPropertyValue(SoakScalePct != SoakScalePct_Default, "SOAKSCALEPCT", SoakScalePct);
      block.SetPropertyValue("DAMAGE_TYPE", DamageType);
      block.SetOrClearPropertyValue(UseDPS != UseDPS_Default, "USEDPS", UseDPS);
      block.SetOrClearPropertyValue(NoStealEffects != NoStealEffects_Default, "NOSTEALEFFECTS", NoStealEffects);
      block.SetOrClearPropertyValue(DurationOverrideMS != DurationOverrideMS_Default, "DURATIONOVERRIDEMS", DurationOverrideMS);
      block.SetOrClearPropertyValue(TargetAhead != TargetAhead_Default, "TARGET_AHEAD_OF_UNIT", TargetAhead);
      block.SetOrClearPropertyValue(AttachLineEmitter != AttachLineEmitter_Default, "ATTACH_LINE_EMITTER", AttachLineEmitter);
      block.SetOrClearPropertyValue(AttachLineEmitterToMaxDistance != AttachLineEmitterToMaxDistance_Default, "ATTACH_LINE_EMITTER_TO_MAX_DISTANCE", AttachLineEmitterToMaxDistance);
      block.SetOrClearPropertyValue(LineEmitterCollides != LineEmitterCollides_Default, "LINE_EMITTER_COLLIDES", LineEmitterCollides);
      block.SetOrClearPropertyValue(DmgRequiresLOS != DmgRequiresLOS_Default, "DAMAGE_REQUIRES_LOS", DmgRequiresLOS);
      block.SetOrClearPropertyValue(AttachOwner != AttachOwner_Default, "ATTACHOWNER", AttachOwner);
      block.SetOrClearPropertyValue(AttachToTarget != AttachToTarget_Default, "ATTACHTOTARGET", AttachToTarget);
      block.SetOrClearPropertyValue(AttachIgnoresUnit != AttachIgnoresUnit_Default, "ATTACHIGNORESUNITS", AttachIgnoresUnit);
      block.SetOrClearPropertyValue(AttachIgnoresObstructions != AttachIgnoresObstruction_Default, "ATTACHIGNORESOBSTRUCTIONS", AttachIgnoresObstructions);
      block.SetOrClearPropertyValue(ScaleAttachDistance != ScaleAttachDistance_Default, "SCALEATTACHDISTANCE", ScaleAttachDistance);
      block.SetOrClearPropertyValue(PlaceOnTarget != PlaceOnTarget_Default, "PLACEONTARGET", PlaceOnTarget);
      block.SetOrClearPropertyValue(ApplyEffects != ApplyEffects_Default, "APPLYEFFECTS", ApplyEffects);
      block.SetOrClearPropertyValue(AlwaysApplyEffects != AlwaysApplyEffects_Default, "APPLYEFFECTSALWAYS", AlwaysApplyEffects);
      block.SetOrClearPropertyValue(CastsFromTarget != CastsFromTarget_Default, "CASTFROMTARGET", CastsFromTarget);
      block.SetOrClearPropertyValue(StatsHidden != StatsHidden_Default, "STATSHIDDEN", StatsHidden);
      block.SetOrClearPropertyValue(MakePet != MakePet_Default, "MAKEPET", MakePet);
      block.SetOrClearPropertyValue(MatchesPetDuration != MatchesPetDuration_Default, "MATCH_PET_DURATION", MatchesPetDuration);
      block.SetPropertyValue("FIREFROMBONE", FireFromBone);
      block.SetPropertyValue("FIREATBONE", FireAtBone);
      block.SetOrClearPropertyValue(CanClone != CanClone_Default, "CAN_CLONE", CanClone);
      block.SetOrClearPropertyValue(CloneCount != CloneCount_Default, "CLONECOUNT", CloneCount);
      block.SetOrClearPropertyValue(MinDelay != MinDelay_Default, "MINDELAY", MinDelay);
      block.SetOrClearPropertyValue(MaxDelay != MaxDelay_Default, "MAXDELAY", MaxDelay);
      block.SetOrClearPropertyValue(MaxUnitsHit != MaxUnitsHit_Default, "MAX_UNITS_HIT", MaxUnitsHit);
      block.SetOrClearPropertyValue(ExecuteSkillCount != ExecuteSkillCount_Default, "EXECUTE_SKILL_COUNT", ExecuteSkillCount);
      block.SetOrClearPropertyValue(TargetCorpses != TargetCorpses_Default, "TARGET_CORPSES", TargetCorpses);

      block.SubBlocks.Clear();
      DataBlock targetUnits = new DataBlock("TARGET_SPECIFIC_UNITS");
      targetUnits.Properties.AddRange(TargetSpecificUnits.Select(s => new DataProperty { Name = "UNIT", Value = s, Type = PropertyType.STRING }));
      block.SubBlocks.Add(targetUnits);

      block.SubBlocks.AddRange(Decendants.Select(d => d.MakeDataBlock()));

      return block;
    }

    public DataBlock Data { get; set; }

    public List<IDataSerializable> Decendants { get; private set; }

    // Properties
    [Description("Apply effect on every update"), Category("Event"), Browsable(true), DisplayName("Always Apply Effects")]
    public bool AlwaysApplyEffects { get; set; }

    [Description("Apply skill effect on event"), Category("Event"), Browsable(true), DisplayName("Apply Effects")]
    public bool ApplyEffects { get; set; }

    [DisplayName("Attaches"), Description("Layout Attaches to the skill user?"), Browsable(true), Category("Attach")]
    public bool Attaches { get; set; }

    [Browsable(true), Category("Attach"), Description("Does the attach ignore Obsructions."), DisplayName("Ignores Obstructions")]
    public bool AttachIgnoresObstructions { get; set; }

    [Description("Does the attach ignore the unit."), DisplayName("Ignores Unit"), Category("Attach"), Browsable(true)]
    public bool AttachIgnoresUnit { get; set; }

    [Description("Attaches line emitter"), DisplayName("Attach Line Emitter"), Browsable(true), Category("Attach")]
    public bool AttachLineEmitter { get; set; }

    [Category("Attach"), Browsable(true), Description("Attaches line emitter to max distance?"), DisplayName("Attach Line Emitter Max Dist.")]
    public bool AttachLineEmitterToMaxDistance { get; set; }

    [Browsable(true), Description("Attach layout to owner"), DisplayName("Attach Owner"), Category("Attach")]
    public bool AttachOwner { get; set; }

    [Description("Attach layout to target"), DisplayName("Attach to Target"), Browsable(true), Category("Attach")]
    public bool AttachToTarget { get; set; }

    [Category("Event"), DisplayName("Clonable"), Description("Event can be cloned")]
    public bool CanClone { get; set; }

    [Browsable(true), Category("Event"), Description("subsequent skill fires from target of event"), DisplayName("Casts From Target")]
    public bool CastsFromTarget { get; set; }

    [Browsable(true), Description("Number of event clones to make"), DisplayName("Clone Count"), Category("Event")]
    public int CloneCount { get; set; }

    [DisplayName("Damage Type"), Category("Event"), Description("Type of damage to inflict")]
    public string DamageType { get; set; }

    [Browsable(true), DisplayName("Damage Reqs. LOS"), Category("Attach"), Description("does the damage require line-of-sight?")]
    public bool DmgRequiresLOS { get; set; }

    [Description("Duration Override in Milliseconds"), DisplayName("Duration"), Category("Event")]
    public int DurationOverrideMS { get; set; }

    [DisplayName("Event Type"), Category("Event"), Description("Event Type")]
    public string EventType { get; set; }

    [Description("How many skills to fire on this event"), Category("Event"), DisplayName("Execute Skill Count")]
    public int ExecuteSkillCount { get; set; }

    [Description("Layout file for event"), Category("Event"), DisplayName("File")]
    public string File { get; set; }

    [Description("Fire at bone"), Category("Event"), DisplayName("Fire at")]
    public string FireAtBone { get; set; }

    [Browsable(true), Category("Event"), Description("Fire from bone"), DisplayName("Fire from")]
    public string FireFromBone { get; set; }

    [Description("Line emitter collides?"), Browsable(true), DisplayName("Line Emitter Collides"), Category("Attach")]
    public bool LineEmitterCollides { get; set; }

    [Browsable(true), DisplayName("Make Pet"), Category("Event"), Description("Created units will be pets")]
    public bool MakePet { get; set; }

    [DisplayName("Matches Pet Duration"), Browsable(true), Category("Event"), Description("Lasts for duration of pet summons")]
    public bool MatchesPetDuration { get; set; }

    [Category("Event"), DisplayName("Max Delay"), Browsable(true), Description("Max delay time (in seconds) between clones")]
    public float MaxDelay { get; set; }

    [DisplayName("Max Units Hit"), Browsable(true), Category("Event"), Description("Maximum number of units the event will hit (0 means no max)")]
    public int MaxUnitsHit { get; set; }

    [Description("Min delay time (in seconds) between clones"), Browsable(true), DisplayName("Min Delay"), Category("Event")]
    public float MinDelay { get; set; }

    [DisplayName("No Steal Effects"), Category("Event"), Description("Can the skill to hp/mana steals? (applicable when doing weapon damage)")]
    public bool NoStealEffects { get; set; }

    [Browsable(true), Description("Place on Target Unit?"), Category("Event"), DisplayName("Place On Target")]
    public bool PlaceOnTarget { get; set; }

    [Browsable(true), Category("Attach"), Description("Attach scales with distance."), DisplayName("Scale Distance")]
    public bool ScaleAttachDistance { get; set; }

    [Browsable(true), Description("Percent damage soak"), DisplayName("Soak Scale %"), Category("Event")]
    public float SoakScalePct { get; set; }

    [Category("Event"), Browsable(true), DisplayName("Hide Stats"), Description("skill stats are hidden")]
    public bool StatsHidden { get; set; }

    [Category("Event"), Description("Distance to target ahead of target unit"), DisplayName("Target Ahead"), Browsable(true)]
    public float TargetAhead { get; set; }

    [Browsable(true), DisplayName("Target Corpses"), Category("Event"), Description("Target corpses")]
    public bool TargetCorpses { get; set; }

    [Description("Event will these units"), DisplayName("Target Units"), Category("Event")]
    public List<string> TargetSpecificUnits { get; private set; }

    [Description("Use Damage Per Second"), DisplayName("Use DPS"), Browsable(true), Category("Event")]
    public bool UseDPS { get; set; }

    [Description("% of weapon damage to add to skill"), DisplayName("Weapon DMG %"), Browsable(true), Category("Event")]
    public float WeaponDmgPct { get; set; }

    public override string ToString()
    {
      return EventType;
    }
  }
}
